package com.sufjan.secret.db;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Logger;


public class DBConnectionManager {

	private static Logger logger = Logger.getLogger(DBConnectionManager.class.getName());
	/**
	 * the very only DBConnectionManager instance
	 */
	private static DBConnectionManager instance;
	
	Connection conn = null;
	/**
	 * how many process get the DBConnectionManager instance and use the
	 * connectionPool
	 */
	private static int clients = 0;

	/**
	 * db driver
	 */
	private Vector<Driver> drivers;

	/**
	 * connection pool
	 */
	private DBConnectionPool pool;

	/**
	 * the props read from the configuration file which are used to create db
	 * connection
	 */
	private Properties props;

	/**
	 * the constructor for that there must be only one DBConnectionManager
	 * instance, so the constructor is private
	 */
	private DBConnectionManager() {
		init();
	}

	/**
	 * get DBConnectionManager instance
	 * 
	 * @return DBConnectionManager instance
	 */
	public synchronized static DBConnectionManager getInstance() {
		if (instance == null) {
			instance = new DBConnectionManager();
		}

		++clients;
		return instance;
	}

	/**
	 * init DBConnectionManager load db driver and create pool
	 */
	private void init() {
		InputStream is = this.getClass().getResourceAsStream("/dbSourse.properties");
		try {
			props = new Properties();
			props.load(is);
		} catch (Exception e) {
		}

		loadDriver();
		createPool();
	}

	/**
	 * load db driver
	 */
	private void loadDriver() {
		drivers = new Vector<Driver>();

		String driverClasses = props.getProperty("driver");
		StringTokenizer st = new StringTokenizer(driverClasses);
		while (st.hasMoreElements()) {
			String driverClassName = st.nextToken().trim();
			try {
				Driver driver = (Driver) Class.forName(driverClassName)
						.newInstance();
				DriverManager.registerDriver(driver);
				drivers.addElement(driver);
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}

	/**
	 * create db connectionPool
	 * 
	 */
	private void createPool() {
		int minConn = Integer.parseInt(props.getProperty("minConnection"));
		int maxConn = Integer.parseInt(props.getProperty("maxConnection"));

		pool = new DBConnectionPool(props.getProperty("username"),
				props.getProperty("password"),
				props.getProperty("connectionURL"), minConn, maxConn);
	}

	/**
	 * get a dbconnection from pool
	 * 
	 * @return db connection
	 */
	public Connection getConnection() {
		int timeout = Integer.parseInt(props.getProperty("timeout"));
		int retry = Integer.parseInt(props.getProperty("retry"));
		

		if (pool != null) {
			conn = pool.getConnection();
		}

		if (conn == null) {
			for (int i = 0; i < retry; i++) {
				conn = getConnection(timeout);
				if (conn != null) {
					break;
				}
			}
		}

		return conn;
	}
	/**
	 * if there's too much process to get connection from the pool there might
	 * not be enough connections so wait some time to reget it
	 */
	private Connection getConnection(long timeout) {
		Connection conn = null;

		try {
			Thread.sleep(timeout);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		conn = pool.getConnection();

		return conn;
	}

	/**
	 * return connection to the pool
	 * 
	 * @param conn
	 *            - connection about to return
	 */
	public void freeConnection(Connection conn) {
		if (pool != null) {
			pool.freeConnection(conn);
		}
	}

	/**
	 * release the pool if there's still some process using the db connection,
	 * wait
	 */
	public void release() {
		if (--clients != 0) {
			return;
		}
		if (pool != null) {
			pool.release();
			@SuppressWarnings("rawtypes")
			Enumeration allDrivers = drivers.elements();
			while (allDrivers.hasMoreElements()) {
				Driver driver = (Driver) allDrivers.nextElement();
				try {
					DriverManager.deregisterDriver(driver);
				} catch (Exception ex) {
					logger.info("Can not deregister driver "
							+ driver.getClass().getName());
					System.out.println("Can not deregister driver "
							+ driver.getClass().getName());
				}
			}
		}
	}
}

